# -*- coding: utf-8 -*-
"""
使用麦克风进行语音识别的演示程序
基于PyAudio实时捕获麦克风输入
"""

import _thread as thread
import time
from time import mktime

# 修改导入方式以兼容不同版本
try:
    import websocket
except ImportError:
    try:
        from websocket import WebSocketApp
        import websocket
    except ImportError:
        print("请安装websocket-client库: pip install websocket-client")
        exit(1)

import base64
import datetime
import hashlib
import hmac
import json
import ssl
from datetime import datetime
from urllib.parse import urlencode
from wsgiref.handlers import format_date_time

# 尝试导入PyAudio，如果失败则提示用户
try:
    import pyaudio
except ImportError:
    print("请安装PyAudio库: pip install pyaudio")
    exit(1)

STATUS_FIRST_FRAME = 0  # 第一帧的标识
STATUS_CONTINUE_FRAME = 1  # 中间帧标识
STATUS_LAST_FRAME = 2  # 最后一帧的标识

# 音频参数
CHUNK = 1280  # 每帧音频数据大小
FORMAT = pyaudio.paInt16  # 16位采样
CHANNELS = 1  # 单声道
RATE = 16000  # 采样率 16kHz
INTERVAL = 0.04  # 发送音频间隔(单位:s)


class Ws_Param(object):
    """WebSocket参数类"""
    def __init__(self, APPID, APIKey, APISecret):
        self.APPID = APPID
        self.APIKey = APIKey
        self.APISecret = APISecret
        self.iat_params = {
            "domain": "slm", "language": "zh_cn", "accent": "mandarin", "dwa": "wpgs", "result":
                {
                    "encoding": "utf8",
                    "compress": "raw",
                    "format": "plain"
                }
        }

    def create_url(self):
        """生成WebSocket连接URL"""
        url = 'ws://iat.xf-yun.com/v1'
        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))

        # 拼接字符串
        signature_origin = "host: " + "iat.xf-yun.com" + "\n"
        signature_origin += "date: " + date + "\n"
        signature_origin += "GET " + "/v1 " + "HTTP/1.1"
        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
                                 digestmod=hashlib.sha256).digest()
        signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')

        authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
            self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
        # 将请求的鉴权参数组合为字典
        v = {
            "authorization": authorization,
            "date": date,
            "host": "iat.xf-yun.com"
        }
        # 拼接鉴权参数，生成url
        url = url + '?' + urlencode(v)
        return url


class MicrophoneRecognizer:
    """麦克风语音识别器类"""
    def __init__(self, app_id, api_key, api_secret):
        """
        初始化麦克风语音识别器
        :param app_id: 讯飞应用ID
        :param api_key: 讯飞API Key
        :param api_secret: 讯飞API Secret
        """
        self.app_id = app_id
        self.api_key = api_key
        self.api_secret = api_secret
        self.recognized_text = ""
        self.is_recording = False
        self.ws = None  # 保存WebSocket连接引用
        self.best_result = ""  # 保存最佳识别结果
        self.result_history = []  # 保存识别结果历史
        
    def _is_valid_result(self, result):
        """判断识别结果是否有效"""
        if not result:
            return False
        # 过滤掉纯标点符号和空白字符
        stripped = result.strip()
        if not stripped:
            return False
        # 检查是否只包含标点符号
        punctuation_marks = set("。，、；：？！“”‘’（）【】《》…—–·.,;:?!\"'()[]<>{}\\/-_=+")
        if all(char in punctuation_marks for char in stripped):
            return False
        return True
        
    def _on_message(self, ws, message):
        """处理WebSocket消息"""
        try:
            message = json.loads(message)
            code = message["header"]["code"]
            status = message["header"]["status"]
            if code != 0:
                print(f"请求错误：{code}")
                ws.close()
            else:
                payload = message.get("payload")
                if payload and "result" in payload:
                    # 获取当前识别结果
                    text_data = payload["result"]["text"]
                    if text_data:
                        try:
                            text = json.loads(str(base64.b64decode(text_data), "utf8"))
                            text_ws = text.get('ws', [])
                            result = ''
                            for i in text_ws:
                                for j in i.get("cw", []):
                                    w = j.get("w", "")
                                    result += w
                            
                            # 显示当前识别结果
                            if result:
                                print(f"识别结果: {result}")
                                # 保存有效的识别结果到历史中
                                if self._is_valid_result(result):
                                    self.result_history.append(result)
                                    self.best_result = result  # 更新最佳结果为最新的有效结果
                        except Exception as e:
                            print(f"文本解析错误: {e}")
                    
                # 当收到最终结果标记时，使用保存的最佳结果
                if status == 2:
                    # 如果有历史有效结果，使用最后一个；否则使用当前最佳结果
                    if self.result_history:
                        self.recognized_text = self.result_history[-1]
                    else:
                        self.recognized_text = self.best_result
                    self.is_recording = False
                    ws.close()
        except Exception as e:
            print(f"消息处理错误: {e}")

    def _on_error(self, ws, error):
        """处理WebSocket错误"""
        print("### error:", error)

    def _on_close(self, ws, close_status_code, close_msg):
        """处理WebSocket关闭"""
        print("### WebSocket连接已关闭 ###")
        self.is_recording = False

    def _on_open(self, ws):
        """处理WebSocket连接打开"""
        self.ws = ws  # 保存WebSocket连接引用
        
        def run(*args):
            status = STATUS_FIRST_FRAME
            self.is_recording = True
            self.best_result = ""  # 重置最佳结果
            self.result_history = []  # 重置结果历史
            
            # 创建wsParam实例
            wsParam = Ws_Param(self.app_id, self.api_key, self.api_secret)
            
            p = pyaudio.PyAudio()
            
            try:
                # 打开音频流
                stream = p.open(format=FORMAT,
                                channels=CHANNELS,
                                rate=RATE,
                                input=True,
                                frames_per_buffer=CHUNK)
            except Exception as e:
                print(f"无法打开音频设备: {e}")
                print("请检查麦克风是否连接正常")
                ws.close()
                return
            
            print("开始录音，说话结束后按Ctrl+C停止...")
            
            try:
                while self.is_recording:
                    # 读取音频数据
                    try:
                        buf = stream.read(CHUNK)
                    except Exception as e:
                        print(f"读取音频数据出错: {e}")
                        break
                        
                    audio = str(base64.b64encode(buf), 'utf-8')

                    # 第一帧处理
                    if status == STATUS_FIRST_FRAME:
                        d = {"header":
                            {
                                "status": 0,
                                "app_id": self.app_id
                            },
                            "parameter": {
                                "iat": wsParam.iat_params
                            },
                            "payload": {
                                "audio":
                                    {
                                        "audio": audio, "sample_rate": RATE, "encoding": "raw"
                                    }
                            }}
                        # 检查连接是否仍然打开
                        if not self.is_recording or not ws.keep_running:
                            break
                        try:
                            ws.send(json.dumps(d))
                        except:
                            break
                        status = STATUS_CONTINUE_FRAME
                        print("已连接，正在录音...")
                    # 中间帧处理
                    elif status == STATUS_CONTINUE_FRAME:
                        d = {"header": {"status": 1,
                                        "app_id": self.app_id},
                             "parameter": {
                                 "iat": wsParam.iat_params
                             },
                             "payload": {
                                 "audio":
                                     {
                                         "audio": audio, "sample_rate": RATE, "encoding": "raw"
                                     }}}
                        # 检查连接是否仍然打开
                        if not self.is_recording or not ws.keep_running:
                            break
                        try:
                            ws.send(json.dumps(d))
                        except:
                            break
                    # 模拟音频采样间隔
                    time.sleep(INTERVAL)
                    
            except KeyboardInterrupt:
                print("\n正在停止录音...")
                # 发送最后一帧
                d = {"header": {"status": 2,
                                "app_id": self.app_id
                                },
                     "parameter": {
                         "iat": wsParam.iat_params
                     },
                     "payload": {
                         "audio":
                             {
                                 "audio": audio, "sample_rate": RATE, "encoding": "raw"
                             }}}
                # 检查连接是否仍然打开
                if ws.keep_running:
                    try:
                        ws.send(json.dumps(d))
                    except:
                        pass
            finally:
                # 关闭音频流
                try:
                    stream.stop_stream()
                    stream.close()
                except:
                    pass
                p.terminate()

        thread.start_new_thread(run, ())

    def recognize(self):
        """
        开始语音识别
        :return: 识别出的文本
        """
        # 重置识别结果
        self.recognized_text = ""
        self.best_result = ""
        self.result_history = []
        self.is_recording = True
        
        # 创建参数实例
        wsParam = Ws_Param(self.app_id, self.api_key, self.api_secret)
        websocket.enableTrace(False)
        wsUrl = wsParam.create_url()
        
        # 创建WebSocket连接
        self.ws = websocket.WebSocketApp(wsUrl, 
                                        on_message=self._on_message, 
                                        on_error=self._on_error, 
                                        on_close=self._on_close)
        self.ws.on_open = self._on_open
        self.ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
        
        return self.recognized_text.strip()


def recognize_speech_mic(app_id, api_key, api_secret):
    """
    使用麦克风进行语音识别的函数接口
    :param app_id: 讯飞应用ID
    :param api_key: 讯飞API Key
    :param api_secret: 讯飞API Secret
    :return: 识别出的文本
    """
    recognizer = MicrophoneRecognizer(app_id, api_key, api_secret)
    print("请说话，我将识别您的语音...")
    return recognizer.recognize()


if __name__ == "__main__":
    # 测试代码
    # 请替换为您的实际APPID、APIKey和APISecret
    APP_ID = 'afed0bdd'
    API_KEY = '8900a3deaab7ae489182e5503cb535d2'
    API_SECRET = 'ZjEyNTgwMjc4YzdlOGNkMDkyYzI0YThj'
    
    result = recognize_speech_mic(APP_ID, API_KEY, API_SECRET)
    print(f"\n最终识别结果: {result}")